/**
 * Copyright (c) 2005-2014 The Apereo Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *             http://opensource.org/licenses/ecl2
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sakaiproject.importer.impl;

import java.io.FileInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.sakaiproject.importer.api.ImportFileParser;
import org.sakaiproject.archive.api.ImportMetadata;
import org.sakaiproject.importer.api.Importable;
import org.sakaiproject.importer.impl.importables.Folder;
import org.sakaiproject.importer.impl.importables.HtmlDocument;
import org.sakaiproject.importer.impl.translators.Bb6AnnouncementTranslator;
import org.sakaiproject.importer.impl.translators.Bb6AssessmentAttemptFilesTranslator;
import org.sakaiproject.importer.impl.translators.Bb6AssessmentAttemptTranslator;
import org.sakaiproject.importer.impl.translators.Bb6CollabSessionTranslator;
import org.sakaiproject.importer.impl.translators.Bb6CourseMembershipTranslator;
import org.sakaiproject.importer.impl.translators.Bb6CourseUploadsTranslator;
import org.sakaiproject.importer.impl.translators.Bb6DiscussionBoardTranslator;
import org.sakaiproject.importer.impl.translators.Bb6ExternalLinkTranslator;
import org.sakaiproject.importer.impl.translators.Bb6GroupUploadsTranslator;
import org.sakaiproject.importer.impl.translators.Bb6HTMLDocumentTranslator;
import org.sakaiproject.importer.impl.translators.Bb6QuestionPoolTranslator;
import org.sakaiproject.importer.impl.translators.Bb6SurveyTranslator;
import org.sakaiproject.importer.impl.translators.Bb6TextDocumentTranslator;
import org.sakaiproject.importer.impl.translators.Bb6SmartTextDocumentTranslator;
import org.sakaiproject.importer.impl.translators.Bb6StaffInfoTranslator;
import org.sakaiproject.importer.impl.translators.Bb6AssessmentTranslator;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;

public class Blackboard6FileParser extends IMSFileParser {
	
	public static final String ASSESSMENT_GROUP = "Assessments";
	public static final String ANNOUNCEMENT_GROUP = "Announcements";
	
	public static final String ASSESSMENT_FILES_DIRECTORY = "TQimages";
	
	public Blackboard6FileParser() {
		// eventually, this will be spring-injected, 
		// but it's ok to hard-code this for now
		addResourceTranslator(new Bb6AnnouncementTranslator());
		addResourceTranslator(new Bb6AssessmentTranslator());
		addResourceTranslator(new Bb6QuestionPoolTranslator());
		addResourceTranslator(new Bb6SurveyTranslator());
		addResourceTranslator(new Bb6AssessmentAttemptTranslator());
		addResourceTranslator(new Bb6StaffInfoTranslator());
		addResourceTranslator(new Bb6HTMLDocumentTranslator());
		addResourceTranslator(new Bb6TextDocumentTranslator());
		addResourceTranslator(new Bb6SmartTextDocumentTranslator());
		addResourceTranslator(new Bb6ExternalLinkTranslator());
		addResourceTranslator(new Bb6CollabSessionTranslator());
		addResourceTranslator(new Bb6AssessmentAttemptFilesTranslator());
		addResourceTranslator(new Bb6CourseUploadsTranslator());
		addResourceTranslator(new Bb6GroupUploadsTranslator());
		addResourceTranslator(new Bb6CourseMembershipTranslator());
		addResourceTranslator(new Bb6DiscussionBoardTranslator());
		resourceHelper = new Bb6ResourceHelper();
		itemHelper = new Bb6ItemHelper();
		fileHelper = new Bb6FileHelper();
		manifestHelper = new Bb6ManifestHelper();
	}
	
	public boolean isValidArchive(InputStream fileData) {
		if (super.isValidArchive(fileData)) {
			//TODO check for compliance with IMS 1.1 DTD
			Document manifest = extractFileAsDOM("/imsmanifest.xml", fileData);

			//String head = extractFileHead("/imsmanifest.xml", fileData);
			// originally head.indexOf("xmlns:bb=\"http://www.blackboard.com/content-packaging/\"") >= 0
			// but we've already read the whole stream, so have to get it from the document
			NodeList nl = manifest.getElementsByTagName("manifest");
			if (nl.getLength() > 0) {
			    Element node = (Element)nl.item(0);
			    String url = node.getAttribute("xmlns:bb");
			    if (url == null || !url.equals("http://www.blackboard.com/content-packaging/"))
				return false;
			} else
			    return false;

			// xmlns:bb means it's v6. However I'm worried about packages generated by other
			// software not being quite right. Since we're most concerned about tests, if it
			// has 5.5 format tests, let 5.5 deal with it.

			return ((XPathHelper.selectNodes("//resource[@type='assessment/x-bb-quiz']", manifest).size() +
				  XPathHelper.selectNodes("//resource[@type='assessment/x-bb-pool']", manifest).size() +
				  XPathHelper.selectNodes("//resource[@type='assessment/x-bb-survey']", manifest).size()) == 0 ||
				(XPathHelper.selectNodes("//resource[@type='assessment/x-bb-qti-test']", manifest).size() +
				 XPathHelper.selectNodes("//resource[@type='assessment/x-bb-qti-pool']", manifest).size()) > 0);
				
			// return (XPathHelper.selectNodes("/manifest/organizations/organization/item",manifest).size() > 0
			// || XPathHelper.selectNodes("/manifest/resources/resource",manifest).size() > 0);

		} else return false;
	}

	protected boolean isCompoundDocument(Node node, Document resourceDescriptor) {
		// the rule we're observing is that any document of type resource/x-bb-document
		// that has more than one child will be treated as a compound document
		return "resource/x-bb-document".equals(XPathHelper.getNodeValue("./@type",node)) &&
	       node.hasChildNodes() && (node.getChildNodes().getLength() > 1);
	}
	
	protected Importable getCompanionForCompoundDocument(Document resourceDescriptor, Folder folder) {
		HtmlDocument html = new HtmlDocument();
		StringBuffer content = new StringBuffer();
		List<Node> fileNodes = XPathHelper.selectNodes("/CONTENT/FILES/FILE", resourceDescriptor);
		content.append("<html>\n");
		content.append("  <head><title>" + folder.getTitle() + "</title></head>\n");
		content.append("  <body>\n");
		content.append("    <p>" + XPathHelper.getNodeValue("/CONTENT/BODY/TEXT", resourceDescriptor) + "</p>\n");
		content.append("    <table border=\"1\">\n");
		for (Node fileNode : fileNodes) {
			String fileName = XPathHelper.getNodeValue("./NAME", fileNode);
			content.append("      <tr><td><a href=\""+ folder.getTitle() + "/" + fileName + "\">" + fileName + "</a></td></tr>\n");
		}
		content.append("    </table>\n");
		content.append("  </body>\n");
		content.append("</html>");
		html.setContent(content.toString());
		html.setTitle(folder.getTitle());
		html.setContextPath(folder.getPath() + folder.getTitle() + "_manifest");
		html.setLegacyGroup(folder.getLegacyGroup());
		// we want the html document to come before the folder in sequence
		html.setSequenceNum(folder.getSequenceNum() - 1);
		return html;
	}

	protected boolean wantsCompanionForCompoundDocument() {
		return true;
	}
	
	protected Collection getCategoriesFromArchive(String pathToData) {
		Collection categories = new ArrayList();
		ImportMetadata im;
		Node topLevelItem;
		String resourceId;
		Node resourceNode;
		String targetType;
		List topLevelItems = manifestHelper.getTopLevelItemNodes(this.archiveManifest);
		for(Iterator i = topLevelItems.iterator(); i.hasNext(); ) {
			topLevelItem = (Node)i.next();
			
			// Each course TOC item has a target type.
			// At present, we only handle the CONTENT 
			// and STAFF_INFO target types,
			// with assessments and announcements being identified
			// separately below.
			resourceId = XPathHelper.getNodeValue("./@identifierref", topLevelItem);
			resourceNode = manifestHelper.getResourceForId(resourceId, this.archiveManifest);
			targetType = XPathHelper.getNodeValue("/COURSETOC/TARGETTYPE/@value", resourceHelper.getDescriptor(resourceNode));
			if (!(("CONTENT".equals(targetType)) || ("STAFF_INFO").equals(targetType))) continue;
			
			im = new BasicImportMetadata();
			im.setId(itemHelper.getId(topLevelItem));
			im.setLegacyTool(itemHelper.getTitle(topLevelItem));
			im.setMandatory(false);
			im.setFileName(".xml");
			im.setSakaiServiceName("ContentHostingService");
			im.setSakaiTool("Resources");
			categories.add(im);
		}
		// Figure out if there are assessments 
		if (XPathHelper.selectNodes("//resource[@type='assessment/x-bb-qti-test']", this.archiveManifest).size() 
				+ XPathHelper.selectNodes("//resource[@type='assessment/x-bb-qti-pool']", this.archiveManifest).size() > 0) {
			im = new BasicImportMetadata();
			im.setId("assessments");
	                im.setLegacyTool(ASSESSMENT_GROUP);
	                im.setMandatory(false);
	                im.setFileName(".xml");
	                im.setSakaiTool("Tests & Quizzes");
	                categories.add(im);
		}
		
		// Figure out if we need an Announcements category
		if (XPathHelper.selectNodes("//resource[@type='resource/x-bb-announcement']", this.archiveManifest).size() > 0) {
			im = new BasicImportMetadata();
			im.setId("announcements");
	                im.setLegacyTool(ANNOUNCEMENT_GROUP);
	                im.setMandatory(false);
	                im.setFileName(".xml");
	                im.setSakaiTool("Announcements");
	                categories.add(im);
		}
		return categories;
	}
	
	protected class Bb6ResourceHelper extends ResourceHelper {
		public String getTitle(Node resourceNode) {
			return resourceNode.getAttributes().getNamedItem("bb:title").getNodeValue().replaceAll("/", "_");
		}
		
		public String getType(Node resourceNode) {
			
			String nodeType = XPathHelper.getNodeValue("./@type", resourceNode);
			
			if ("resource/x-bb-document".equals(nodeType)) {
				/*
				 * Since we've gotten a bb-document, we need to figure out what kind it is. Known possible are:
				 *   1. x-bb-externallink
				 *   2. x-bb-document
				 *     a. Plain text
				 *     b. Smart text
				 *     c. HTML
				 * The reason we have to do this is that all the above types are listed as type "resource/x-bb-document"
				 *  in the top level resource node. Their true nature is found with the XML descriptor (.dat file) 
				 */
				
				if(resourceNode.hasChildNodes()) {
					// If it has child-nodes (files, usually) we don't want to parse the actual document
					return nodeType;
				}
				
				String subType = XPathHelper.getNodeValue("/CONTENT/CONTENTHANDLER/@value", resourceHelper.getDescriptor(resourceNode));	
				if ("resource/x-bb-externallink".equals(subType)) {
					nodeType = "resource/x-bb-externallink";
				} else if ("resource/x-bb-asmt-test-link".equals(subType)) {
					nodeType = "resource/x-bb-asmt-test-link";
				} else {
					String docType = XPathHelper.getNodeValue("/CONTENT/BODY/TYPE/@value", resourceHelper.getDescriptor(resourceNode));
					if ("H".equals(docType)) {
						nodeType = "resource/x-bb-document-html";
					} else if ("P".equals(docType)) {
						nodeType = "resource/x-bb-document-plain-text";
					} else if ("S".equals(docType)) {
						nodeType = "resource/x-bb-document-smart-text";
					}
				}
			}
			
			return nodeType;
		}
		
		public String getId(Node resourceNode) {
			return XPathHelper.getNodeValue("./@identifier", resourceNode);
		}
		
		public Document getDescriptor(Node resourceNode) {
			try {
				String descriptorFilename = resourceNode.getAttributes().getNamedItem("bb:file").getNodeValue();
				DocumentBuilder docBuilder;
				docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			    InputStream fis = new FileInputStream(pathToData + "/" + descriptorFilename);
			    return (Document) docBuilder.parse(fis);
			} catch (Exception e) {
				return null;
			}
		}

		public String getDescription(Node resourceNode) {
			Document descriptor = resourceHelper.getDescriptor(resourceNode);
			return XPathHelper.getNodeValue("/CONTENT/BODY/TEXT", descriptor);
		}
		
		public boolean isFolder(Document resourceDescriptor) {
			return "true".equals(XPathHelper.getNodeValue("/CONTENT/FLAGS/ISFOLDER/@value", resourceDescriptor));
		}
	}
	
	protected class Bb6ItemHelper extends ItemHelper {

		public String getId(Node itemNode) {
			return XPathHelper.getNodeValue("./@identifier", itemNode);
		}

		public String getTitle(Node itemNode) {
			return XPathHelper.getNodeValue("./title",itemNode);
		}

		public String getDescription(Node itemNode) {
			String resourceId = XPathHelper.getNodeValue("./@identifierref", itemNode);
			Node resourceNode = manifestHelper.getResourceForId(resourceId, archiveManifest);
			return resourceHelper.getDescription(resourceNode);
		}
		
	}
	
	protected class Bb6ManifestHelper extends ManifestHelper {

		public List getItemNodes(Document manifest) {
			return XPathHelper.selectNodes("//item", manifest);
		}

		public Node getResourceForId(String resourceId, Document manifest) {
			return XPathHelper.selectNode("//resource[@identifier='" + resourceId + "']",archiveManifest);
		}

		public List getResourceNodes(Document manifest) {
			return XPathHelper.selectNodes("//resource", manifest);
		}

		public List getTopLevelItemNodes(Document manifest) {
			return XPathHelper.selectNodes("//organization/item", manifest);
		}

	}
	
	protected class Bb6FileHelper extends FileHelper {
		
		public byte[] getFileBytesForNode(Node node, String contextPath) throws IOException {
			//for Bb we ignore the contextPath...
			String basePath = XPathHelper.getNodeValue("./@identifier",node.getParentNode());
			String fileHref = XPathHelper.getNodeValue("./@href", node).replaceAll("\\\\", "/");
			String filePath = basePath + "/" + fileHref;
			return getBytesFromFile(new File(pathToData + "/" + filePath));
		}	
	
		public String getFilePathForNode(Node node, String contextPath) {
			// for files that are part of an assessment, we're going to
			// tack on an extra container folder to the path.
			String parentType = XPathHelper.getNodeValue("../@type", node);
			if ("assessment/x-bb-qti-pool".equals(parentType) || "assessment/x-bb-qti-test".equals(parentType)) {
				contextPath = contextPath + "/" + ASSESSMENT_FILES_DIRECTORY;
			}
			String fileHref = XPathHelper.getNodeValue("./@href", node);
			return contextPath + "/" + fileHref.replaceAll("\\\\", "/");
		}
		
	}
	// RU: Needed this for 2.6 -- Cat
	public ImportFileParser newParser() {
		return new Blackboard6FileParser();
	}

}
